index.ts ➔ writer   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 33
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 33
rs 9.28
c 0
b 0
f 0
cc 4
1
import {resolve} from "path"
2
import {extractDefaults, regexpize} from './utils'
3
import {
4
  $exists, $unlink, readlineSync
5
} from './fs'
6
import schema = require("./schema.json")
7
import type {Options} from './options.types'
8
import replaceMultiplicated = require('./replaceMultiplicated')
9
import collector = require('./collector')
10
import rewrite = require('./rewrite')
11
import type {InternalOptions, WithSource} from './$defs.types'
12
13
type Opts = Required<Options>
14
15
const {keys: $keys} = Object
16
, defaultOptions = extractDefaults(schema) as Opts
17
, {
18
  title,
19
  signature,
20
  templateEol,
21
  properties: {template: {$comment: templatePath}}
22
} = schema
23
, defaultTemplate = readlineSync(resolve(__dirname, templatePath), templateEol),
24
25
creator8 = (opts?: Options) => {
26
  const options = makeOpts(opts)
27
28
  return {
29
    postcssPlugin: title,
30
    prepare: (result: {
31
      warn: (arg: string) => any
32
      root: WithSource
33
    }) => {
34
      //TODO #12 template update check
35
36
      /* istanbul ignore next `source === undefined` for manually created node with `.decl` */
37
      if (!result.root?.source?.input.file)
38
        return {}
39
40
      try {
41
        const warn = optsCheck(options)
42
43
        warn && result.warn(warn)
44
      } catch ({message}) {
45
        // TODO throw error
46
        result.warn(message)
47
48
        return {}
49
      }
50
51
      // https://jsbench.me/q5km8xdgbb
52
      const identifiers: Record<string, true> = {}
53
54
      return {
55
        RuleExit: collector(identifiers, options),
56
        RootExit: writer(identifiers, options)
57
      }
58
    }
59
  } //as Plugin
60
}
61
62
creator8.postcss = true
63
64
export = creator8
65
66
function optsCheck({
67
  destination,
68
  identifierParser
69
}: {destination: any} & Pick<InternalOptions, "identifierParser">) {
70
  if (!(destination === false || destination !== null && typeof destination === "object"))
71
    throw new Error("Destination is of wrong type")
72
73
  if (!identifierParser.flags.includes('g'))
74
    return 'identifierParser without global flag may take only first occurance'
75
76
  return
77
}
78
79
function makeOpts(opts?: Options) {
80
  const options = !opts ? defaultOptions : {...defaultOptions, ...opts}
81
  , {
82
    eol,
83
    destination,
84
    //TODO several keywords?
85
    identifierKeyword,
86
    identifierMatchIndex,
87
    identifierCleanupReplace
88
  } = options
89
90
  return {
91
    eol,
92
    destination,
93
    identifierKeyword,
94
    identifierMatchIndex,
95
    identifierCleanupReplace,
96
    ...internalOpts(options)
97
  }
98
}
99
100
function internalOpts({
101
  eol,
102
  template: templatePath,
103
  identifierPattern: cssP,
104
  identifierCleanupPattern: escapedP,
105
  allowedAtRules: atRules,
106
  checkMode
107
}: Pick<Opts, "eol"|"template"|"identifierPattern"|"identifierCleanupPattern"|"allowedAtRules"|"checkMode">) :InternalOptions {
108
  const identifierParser = regexpize(cssP, "g")
109
  , identifierCleanupParser = regexpize(escapedP, "g")
110
  //TODO check `templatePath === ""`
111
  , templateContent = typeof templatePath === "string"
112
  // TODO not sync
113
  ? readlineSync(templatePath, eol)
114
  : defaultTemplate
115
116
  , allowedAtRuleNames = new Set(atRules)
117
118
  return {
119
    identifierParser,
120
    identifierCleanupParser,
121
    templateContent,
122
    allowedAtRuleNames,
123
    checkMode: checkMode ?? process.env.NODE_ENV === "production"
124
  }
125
}
126
127
function writer(
128
  identifiers: Record<string, any>,
129
  {
130
    eol,
131
    templateContent,
132
    identifierKeyword,
133
    destination,
134
    checkMode
135
  }: Pick<Opts, "eol"|"identifierKeyword"|"destination">
136
  & Pick<InternalOptions, "templateContent"|"checkMode">
137
) {
138
  return async({source}: WithSource) => {
139
    //TODO ? Change `sort` with option
140
    const keys = $keys(identifiers).sort()
141
    , file = source!.input.file!
142
    , target = `${file}.d.ts`
143
    , lines = replaceMultiplicated(
144
      signature.concat(templateContent),
145
      identifierKeyword,
146
      keys
147
    )
148
149
    if (destination === false) {
150
      if (keys.length !== 0)
151
        return await rewrite(target, lines, eol, checkMode)
152
153
      if (checkMode && await $exists(target))
154
        throw new Error(`File "${target}" should not exist`)
155
156
      await $unlink(target)
157
    } else
158
      destination[file] = lines
159
  }
160
}
161